{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# OpenAI Tools\n",
    "\n",
    "These output parsers extract tool calls from OpenAI’s function calling API responses. This means they are only usable with models that support function calling, and specifically the latest `tools` and `tool_choice` parameters. We recommend familiarizing yourself with [function calling](/docs/modules/model_io/chat/function_calling) before reading this guide.\n",
    "\n",
    "There are a few different variants of output parsers:\n",
    "\n",
    "- [`JsonOutputToolsParser`](https://api.js.langchain.com/classes/langchain_output_parsers.JsonOutputToolsParser.html): Returns the arguments of the function call as JSON\n",
    "- [`JsonOutputKeyToolsParser`](https://api.js.langchain.com/classes/langchain_output_parsers.JsonOutputKeyToolsParser.html): Returns the value of specific key in the function call as JSON"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { ChatPromptTemplate } from '@langchain/core/prompts';\n",
    "import { ChatOpenAI } from '@langchain/openai';"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "const properties = {\n",
    "  setup: {\n",
    "    type: \"string\",\n",
    "    description: \"The setup for the joke\"\n",
    "  },\n",
    "  punchline: {\n",
    "    type: \"string\",\n",
    "    description: \"The joke's punchline\"\n",
    "  }\n",
    "};\n",
    "\n",
    "const tool = {\n",
    "  type: \"function\" as const,\n",
    "  function: {\n",
    "    name: \"joke\",\n",
    "    description: \"Joke to tell user.\",\n",
    "    parameters: {\n",
    "      $schema: \"http://json-schema.org/draft-07/schema#\",\n",
    "      title: \"Joke\",\n",
    "      type: \"object\",\n",
    "      properties,\n",
    "      required: [\"setup\", \"punchline\"]\n",
    "    },\n",
    "  },\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { ChatPromptTemplate } from \"@langchain/core/prompts\";\n",
    "\n",
    "const llm = new ChatOpenAI();\n",
    "\n",
    "// Use `.bind` to attach the tool to the model\n",
    "const llmWithTools = llm.bind({\n",
    "  tools: [tool],\n",
    "  // Optionally, we can pass the tool to the `tool_choice` parameter to\n",
    "  // force the model to call the tool.\n",
    "  tool_choice: tool,\n",
    "});\n",
    "\n",
    "const prompt = ChatPromptTemplate.fromMessages([\n",
    "  [\"system\", \"You are the funniest comedian, tell the user a joke about their topic.\"],\n",
    "  [\"human\", \"Topic: {topic}\"]\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can use LCEL to pipe our prompt and LLM together."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "const chain = prompt.pipe(llmWithTools);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "const result = await chain.invoke({ topic: \"Large Language Models\" });"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\n",
       "  function_call: \u001b[90mundefined\u001b[39m,\n",
       "  tool_calls: [\n",
       "    {\n",
       "      id: \u001b[32m\"call_vo9oYcHXKWzS6bJ4bK7Eghmz\"\u001b[39m,\n",
       "      type: \u001b[32m\"function\"\u001b[39m,\n",
       "      function: {\n",
       "        name: \u001b[32m\"joke\"\u001b[39m,\n",
       "        arguments: \u001b[32m\"{\\n\"\u001b[39m +\n",
       "          \u001b[32m'  \"setup\": \"Why did the large language model go on a diet?\",\\n'\u001b[39m +\n",
       "          \u001b[32m'  \"punchline\": \"It wanted to reduce i'\u001b[39m... 17 more characters\n",
       "      }\n",
       "    }\n",
       "  ]\n",
       "}"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "result.additional_kwargs;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> #### Inspect the [LangSmith trace](https://smith.langchain.com/public/f2f34c8d-8193-40cb-b3ef-f186fb4de73e/r) from the call above"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `JsonOutputToolsParser`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { JsonOutputToolsParser } from \"langchain/output_parsers\";\n",
    "\n",
    "const outputParser = new JsonOutputToolsParser();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "const chain = prompt.pipe(llmWithTools).pipe(outputParser);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[\n",
       "  {\n",
       "    type: \u001b[32m\"joke\"\u001b[39m,\n",
       "    args: {\n",
       "      setup: \u001b[32m\"Why did the large language model go to therapy?\"\u001b[39m,\n",
       "      punchline: \u001b[32m\"It had too many layers!\"\u001b[39m\n",
       "    }\n",
       "  }\n",
       "]"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "await chain.invoke({ topic: \"Large Language Models\" });"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> #### Inspect the [LangSmith trace](https://smith.langchain.com/public/61ce7b9f-d462-499e-be65-8a165d2b47a7/r) with the `JsonOutputToolsParser`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `JsonOutputKeyToolsParser`\n",
    "\n",
    "This merely extracts a single key from the returned response. This is useful for when you are passing in a single tool and just want it’s arguments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { JsonOutputKeyToolsParser } from \"langchain/output_parsers\";\n",
    "\n",
    "const outputParser = new JsonOutputKeyToolsParser({ keyName: \"joke\" });"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "const chain = prompt.pipe(llmWithTools).pipe(outputParser);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[\n",
       "  {\n",
       "    setup: \u001b[32m\"Why did the large language model go to therapy?\"\u001b[39m,\n",
       "    punchline: \u001b[32m\"It had too many layers!\"\u001b[39m\n",
       "  }\n",
       "]"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "await chain.invoke({ topic: \"Large Language Models\" })"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> #### Inspect the [LangSmith trace](https://smith.langchain.com/public/2c9c93d2-d789-4e45-9f9f-e942eace8aed/r) with the `JsonOutputKeyToolsParser`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some LLMs have support for calling multiple tools in a single response. Because of this, the result of invoking `JsonOutputKeyToolsParser` is always an array. If you would only like a single result to be returned, you can specify `returnSingle` in the constructor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "const outputParserSingle = new JsonOutputKeyToolsParser({\n",
    "  keyName: \"joke\",\n",
    "  returnSingle: true,\n",
    "});"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "const chain = prompt.pipe(llmWithTools);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "const response = await chain.invoke({ topic: \"Large Language Models\" });"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\n",
       "  setup: \u001b[32m\"Why did the large language model go on a diet?\"\u001b[39m,\n",
       "  punchline: \u001b[32m\"It wanted to shed some excess bytes!\"\u001b[39m\n",
       "}"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "await outputParserSingle.invoke(response)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> #### See the [LangSmith trace](https://smith.langchain.com/public/c05e0409-8085-487d-aee2-2d42b64b9f6d/r) from this output parser."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Deno",
   "language": "typescript",
   "name": "deno"
  },
  "language_info": {
   "file_extension": ".ts",
   "mimetype": "text/x.typescript",
   "name": "typescript",
   "nb_converter": "script",
   "pygments_lexer": "typescript",
   "version": "5.3.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
